# 
#  SYS_SIZE is the number of clicks (16 bytes) to be loaded.
#  0x3000 is 0x30000 bytes = 196kB, more than enough for current
#  versions of linux
# 
#  SYSTEM的大小, 这个值比实际的文件要大。下面从磁盘中加载时，会加载这么多
#  保证实际编译出来并写入Image中的文件会被全部加载进入。
#  注意，其大小是以16字节为单位的，因此实际为值*16
.equ SYSSIZE, 0x3000			# 内核最大字节量。有限制，不能超196KB
# 
# 	bootsect.s		(C) 1991 Linus Torvalds
# 
#  bootsect.s is loaded at 0x7c00 by the bios-startup routines, and moves
#  iself out of the way to address 0x90000, and jumps there.
# 
#  It then loads 'setup' directly after itself (0x90200), and the system
#  at 0x10000, using BIOS interrupts. 
# 
#  NOTE#  currently system is at most 8*65536 bytes long. This should be no
#  problem, even in the future. I want to keep it simple. This 512 kB
#  kernel size should be enough, especially as this doesn't contain the
#  buffer cache as in minix
# 
#  The loader has been made as simple as possible, and continuos
#  read errors will result in a unbreakable loop. Reboot by hand. It
#  loads pretty fast by getting whole sectors at a time whenever possible.
#  boot主要功能：加载setup和system，检测每磁盘扇区号，根文件系统设备号
; .globl begtext, begdata, begbss, endtext, enddata, endbss
; .text
; begtext:
; .data
; begdata:
; .bss
; begbss:

	.text
	.code16
	.globl _start

# 以下定义了各模块在整个内存中的位置，即各段的地址
	.equ SETUPLEN, 4			# nr of setup-sectors, setup模块的扇区数量
	.equ BOOTSEG, 0x07c0		# original address of boot-sector, boot seg超始段
	.equ INITSEG, 0x9000		# we move boot here - out of the way，boot移动后的段
	.equ SETUPSEG, 0x9020		# setup starts here, setup的段号
	.equ SYSSEG, 0x1000			# system loaded at 0x10000 (65536). system模块的加载的段，对应64KB处
	.equ ENDSEG, SYSSEG + SYSSIZE	# where to stop loading, SYSSEG的结束地址

#  ROOT_DEV:	0x000 - same type of floppy as boot.
# 		0x301 - first partition on first drive etc
#  缺省的根目录设备号，当Makefile中未指定时，使用这里缺省的值
#  高16位代表主设备major，低16位代表次设备minor。
#  major=3表示硬盘；minor=0表示整个硬盘，1/2/3/4分别表示4个分区。 5表示第二块硬盘，6是第2块硬盘的第1个分区
#  因此0X306表示第2块硬盘的第1个分区。
#  在这里目前没什么用，，因为生成工具Build中写入了一个缺省值进去，即0x301
    .equ ROOT_DEV, 0x301   			#  缺省根目录设备号,

#  entry _start
_start:
	#  计算机启动后，默认加载Image的第0个扇区，即bootsect到0x7c00处运行。但是
	#  bootsect会将自己搬至高处0x90000，以便将1MB的低处大部分区域留给内核使用
	#  搬运512字节数据，从0x7c00搬至0x90000。即将Bootsect搬至0x90000运行
	#  设置拷贝起始和目的的段寄存器ds、es
	#  相当于调用memcpy(0x90000, 0x7c00, 512)
	mov 	$BOOTSEG, %ax		#  mov	ax,#BOOTSEG-e
	mov    %ax,%ds				#  mov	ds,ax
	mov    $INITSEG,%ax			#  mov	ax,#INITSEG
	mov    %ax,%es

	#  使用rep movsw重复搬。共256个16位，即512字节
	mov    $256,%cx
	sub    %si,%si				#  si和di相当于数组索引
	sub    %di,%di
	rep movsw %ds:(%si),%es:(%di)	#  rep movw

	#  远跳转，进入到0x90000:go运行。由于使用了符号$go，会被转换成偏移量，
	#  所以跳到了90000处的go处运行。虽然看起来没什么变化，但程序的位置是变了
	#  相当于goto $go
	#  注意，这里跳转以后，CS的值变成了INITSEG
	#  为什么要远跳转，因为0x90000已经超过了EIP（16位）的表示范围，所以将CS进行了设置，
	#  然后与EIP进行结合，生成最终执行的地址
	ljmp   $INITSEG,$go		#  jmpi	go,INITSEG

	#  注意，下面的代码已经跑到0x90000处执行
	#  重置段寄存器，各cs一样。设置成0x90000
	#  这样后续所有的符号、内在均使用基地址0x90000。如果不这样设置，会访问成0x7c00处的内容。
	#  由于后面会加载system覆盖这一部分，所以不能再使用0x7c00处的内容
	#  此外，以下代码均运行在大于64KB地址处，因此需要使用这种段访问的方式
go:	mov    %cs,%ax			#  mov	ax,cs
	mov    %ax,%ds			#  mov	ds,ax
	mov    %ax,%es			#  mov	es,ax
	mov    %ax,%ss			#  mov	ss,ax
	
	#  put stack at 0x9ff00.
	#  设置bootsect程序使用的栈地址，结合ss，实际的值为0x90000+0xff00，即0x9ff00
	#  这个值比较大，能保证不占用botosect和setup的空间
	#  之所以要设置，是因为后面有使用Int、call指令，需要用到栈
	#  bios肯定要使用栈，不过这里不打算使用它的，因为不知道指向的哪里，所以自己设置
	mov    $0xff00,%sp		#   mov	sp,#0xFF00		#  arbitrary value >>512

#  load the setup-sectors directly after the bootblock.
#  Note that 'es' is already set up.

	#  读取setup(位于磁盘扇区2，从1开始编号)的到内存0x90200处，共读取4个扇区
	#  在build中有检查setup的大小不能超过4个扇区
load_setup:
    #  while (1) {
	#  相相当于调用BIOS提供的函数：load_disk(软盘，0x90200, 起始2, 扇区数量4)
	#  代码量比较小，不需要考虑跨柱面、磁道等问题
	mov    $0x0000,%dx		#  mov	dx,#0x0000		#  dh: 磁头号；dl：驱动器号：drive 0, head 0，读软盘，磁头0
	mov    $0x0002,%cx		#  mov	cx,#0x0002		#  ch: 磁道号低8位，cl开始扇区：2
	mov    $0x200,%bx		#  mov	bx,#0x0200
	mov    $0x0200+SETUPLEN,%ax		#  mov	ax,#0x0200+SETUPLEN	#  ah=2，读扇区, al：读扇区数nr of sectors
	int    $0x13		#  int	0x13			#  read it

	#  if 成功，跳转到ok_load_setup
	#  jnc	ok_load_setup
	jae		ok_load_setup		#  jnc	$ok_load_setup		#  ok - continue

	#  else 读取错误，调用bios中断复位磁盘后卡死重试，继续尝试读取reset_disk()
	mov    $0x0000,%dx			#  mov	dx,#0x0000
	mov    $0x0000,%ax			#  mov	ax,#0x0000		#  reset the diskette
	int	$0x13

	#  跳转到load_setup，继续尝试读取。差不多就是一个死循环吧
	jmp		load_setup		#  j	load_setup
	#  } 一个死循环

ok_load_setup:
	#  取软盘的驱动器参数，主要是获取下面的每磁道扇区数，用于以后使用
	#  相当于get_disk_data()
	mov    $0x0,%dl			#  mov	dl,#0x00
	mov    $0x800,%ax		#  mov	ax,#0x0800		#  AH=8 is get drive parameters
	int    $0x13			#  int	0x13

	#  上面调用后，cl保存每磁盘最大扇区数。连同边下的ch清0
	#  使得cx保存的值就是磁盘最大扇区数量，这个数据先保存到sectors中
	#  这个东西会在后面读system用到，以及判断根文件系统在哪儿，其余地方没看到使用。也会用于判断软盘的种类
	mov    $0x0,%ch			#  mov	ch,#0x00
	mov    %cx, sectors		#  mov	sectors,cx

	#  上边的int 13会改动es？这里重新加载一下
	mov    $INITSEG,%ax		#  mov	ax,#INITSEG
	mov    %ax,%es			#  mov	es,ax

	#  获取当前屏幕上光标的行和列，先放在dl和dh中,这样下边就可以在当前位置上显示字符串
	#  (row, col) = get_cursor()
	mov    $0x3,%ah			#  mov	ah,#0x03		#  read cursor pos
	xor    %bh,%bh			#  xor	bh,bh
	int    $0x10			#  int	0x10
	
	#  display(row, col, string)
	#  显示\r\nLoading system ...通过bios中断
	#  显示的位置由dh/dl决定，在前面已经获得过了这两个数据
	#  相当于是调用了printf("\r\nloading message\r\n");
	mov    $24,%cx		#  mov	cx,#24,      #  字符数？
	mov    $0x7,%bx		#  mov	bx,#0x0007		#  page 0, attribute 7 (normal)
	mov    $msg1,%bp	#  mov	bp,#msg1
	mov    $0x1301,%ax		#  mov	ax,#0x1301		#  write string, move cursor
	int    $0x10		#  int	0x10

	#  ok, we've written the message, now
	#  we want to load the system (at 0x10000)
	#  现在开始加载system模块, 加载的位置为低端的64KB处
	#  这里使用es段来进行加载, 设置好基地址
	mov    $SYSSEG,%ax		#  mov	ax,#SYSSEG
	mov    %ax,%es			#  mov	es,ax		#  segment of 0x010000
	call	read_it			#  加载system模块
	call	kill_motor		#  关闭马达，和硬件有关，略

#  After that we check which root-device to use. If the device is
#  defined (# = 0), nothing is done and the given device is used.
#  Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
#  on the number of sectors that the BIOS reports currently.
	#  取根设备号。由于我们已经在Makefile中给定，所以下面的代码其实不需要看，因为并不执行
	#  取设置号。由build写入，位置为508和509。当值为0时，即build时没有传入设备号
	mov    root_dev,%ax		#  mov	ax,root_dev
	cmp    $0x0,%ax				#  cmp	ax,#0
	jne		root_defined

	#  当没有传入设备号时，根据每磁盘扇区数进行检测, 检查出设备号类型。不同的的数量对应于不同类型的软盘
	#  我们不用软盘当作根设备，而且基本不出错，所以这个略过。
	mov    $sectors,%bx		#  mov	bx,sectors
	mov    $0x208,%ax			#  mov	ax,#0x0208		#  /dev/ps0 - 1.2Mb，某种软盘，作为根文件系统
	cmp    $0xf,%bx			#  cmp	bx,#15
	je		root_defined
	mov    $0x21c,%ax			#  mov	ax,#0x021c		#  /dev/PS0 - 1.44Mb，作为根文件系统
	cmp    $18,%bx			#  cmp	bx,#18
	je		root_defined
undef_root:
	jmp 	undef_root
root_defined:
	#  有定义设备号，或者检测到了所在的设备号，重新保存到root_dev中。
	mov    %ax,root_dev		#  mov	root_dev,ax

#  after that (everyting loaded), we jump to
#  the setup-routine loaded directly after
#  the bootblock:

	#  跳转到setup中运行。注意仍然使用了分段的处理。段起始地址为0x90200
	#  setup在链接时，也指定的是-Ttext 0，所以这里要用$0来指定
	 ljmp   $SETUPSEG,$0x0		#  jmpi	0,SETUPSEG

#  This routine loads the system at address 0x10000, making sure
#  no 64kB boundaries are crossed. We try to load it as fast as
#  possible, loading whole tracks whenever we can.
# 
#  in:	es - starting address segment (normally 0x1000)
#  读取内核到0x1000处。在调用前已经将es设置成了64KB起始位置
#  读取过程中使用了int 13来进行读取。另外，在读取过程中由于考虑了磁盘的chs结构，所以
#  下面的代码写起来比较复杂。具体分析汇编代码的意义不大。其就是通过CHS+BIOS中断读扇区
#  而实际上现在有更简单的LBA模式读取代码，不用搞那么复杂。
read_it:
	#  检查es的合法性，必须64KB边界？
	mov %es, %ax 	#  mov ax,es
	test $0xfff, %ax		#  test ax,#0x0fff
die:	
	jne die			#  es must be at 64kB boundary,非64KB死机

	#  bx为写目标地址偏移号
	xor    %bx,%bx		#  xor bx,bx		#  bx is starting address within segment
rp_read:
	#  每次写es会调整，判断其es是否超过了边界，大小为0x30000，超过则加载完毕
	mov    %es,%ax		#  mov ax,es
	cmp    $ENDSEG,%ax		#  cmp ax,#ENDSEG		#  have we loaded all yet?, 
	jb 	ok1_read
	ret			#  超过返回
ok1_read:
	#  seg cs
	mov   sectors,%ax		#  mov ax,sectors
	sub    sread,%ax		#  sub ax,sread
	mov    %ax,%cx			#  mov cx,ax
	shl    $0x9,%cx			#  shl cx,#9
	add    %bx,%cx			#  add cx,bx
	jnc 	ok2_read
	je 		ok2_read
	xor    %ax,%ax			#  xor ax,ax
	sub    %bx,%ax			#  sub ax,bx
	shr    $0x9,%ax			#  shr ax,#9
ok2_read:
	call 	read_track
	mov    %ax,%cx			#  mov cx,ax
	add    sread,%ax			#  add ax,sread
	#  seg cs
	cmp    sectors,%ax 	#  cmp ax,sectors
	jne 	ok3_read
 	mov    $0x1,%ax		#  mov ax,#1
	sub    head,%ax		#  sub ax,head
	jne 	ok4_read
	incw track
ok4_read:
	mov    %ax,head			#  mov head,ax
	xor    %ax,%ax			#  xor ax,ax
ok3_read:
	mov    %ax,sread			#  mov sread,ax
	shl    $0x9,%cx				#  shl cx,#9
	add    %cx,%bx			#  add bx,cx
	jnc 	rp_read
	mov    %es,%ax			#  mov ax,es
	add    $0x1000,%ax		#  add ax,#0x1000
	mov    %ax,%es			#  mov es,ax
	xor    %bx,%bx			#  xor bx,bx
	jmp 	rp_read

read_track:
	push %ax
	push %bx
	push %cx
	push %dx
	mov    track,%dx			#  mov dx,track
	mov    sread,%cx			#  mov cx,sread
	inc %cx
	mov    %dl,%ch				#  mov ch,dl
	mov    head,%dx				#  mov dx,head
	mov    %dl,%dh				#  mov dh,dl
	mov    $0x0,%dl				#  mov dl,#0
	and    $0x100,%dx			#  and dx,#0x0100
	mov    $0x2,%ah				#  mov ah,#2
	int $0x13
	jc 	bad_rt
	pop %dx
	pop %cx
	pop %bx
	pop %ax
	ret
bad_rt:	 mov    $0x0,%ax		#  mov ax,#0
	mov    $0x0,%dx				#  mov dx,#0
	int $0x13
	pop %dx
	pop %cx
	pop %bx
	pop %ax
	jmp read_track

# /*
#  * This procedure turns off the floppy drive motor, so
#  * that we enter the kernel in a known state, and
#  * don't have to worry about it later.
#  */
#  向软盘发送命令来关闭马达
kill_motor:
	push %dx
	mov    $0x3f2,%dx		#  mov dx,#0x3f2
	mov    $0x0,%al			#  mov al,#0
	out    %al,(%dx)		#  outb
	pop %dx
	ret

#  下面是读system中的一些中间性的变量，用于辅助计算
sread:	.word 1+SETUPLEN	#  sectors read of current track
head:	.word 0			#  current head
track:	.word 0			#  current track

#  每磁盘扇区数，用于软盘类型的判断和临时计算
#  相当于unsigned short sectors = 0;
sectors:
	.word 0

#  相当于const char * msg1 = "\r\nLoading system..\r\n";
	#  显示的字符串，13、10分别是回车和换行
msg1:
	.byte 13,10			#  13和10是回车和换行
	.ascii "Loading system ..."
	.byte 13,10,13,10

	#  在508处放根目录的设备号
	#  相当于unsigned short root_dev = ROOT_DEV;
	.org 508
root_dev:
	.word ROOT_DEV		#  根目录设备号

	#  定位到510字节处，放引导标志
	.org 510
	#  相当于unsigned short boot_flag = 0xAA55;
boot_flag:
	.word 0xAA55		#  启动引导标志

; .text
; endtext:
; .data
; enddata:
; .bss
; endbss:
